﻿package net.chrisjrock.display
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.text.TextField;
	import flash.text.TextLineMetrics;
	
	/**
	 * ...
	 * @author Christopher J. Rock
	 */
	public class ScriptManager extends EventDispatcher
	{
		public static const SCRIPT_COMPLETE:String = "scriptComplete";
		public static const ALL_SCRIPTS_COMPLETE:String = "allScriptsComplete";
		
		protected var _textField:TextField;
		
		protected var _scripts:Vector.<String>;
		protected var _scriptNum:int;
		
		protected var _writeRate:Number = .025;
		protected var _writePos:Number = 0;
		protected var _writing:Boolean;
		protected var _writeComplete:Boolean;
		
		protected var _autoUpdate:Boolean;
		protected var _timePrev:Number;
		
		protected var _initComplete:Boolean;

		public function ScriptManager ():void
		{
		}
		
		public function destroyExternal ():void
		{
			if (_textField)
			{
				_textField.removeEventListener(Event.ENTER_FRAME , writeFrameHandler);
			}
		}
		
		public function destroyInternal ():void
		{
			_textField = null;
		}
		
		public function init ():void
		{
			if (!_textField)
			{
				return;
			}
			
			_scripts = new Vector.<String>();
			
			_initComplete = true;
		}
		
		public function setScripts (scripts:Vector.<String> , autoStart:Boolean = true):void
		{
			if (!_initComplete || scripts == null)
			{
				return;
			}
			
			for (var i:int ; i < scripts.length ; i++)
			{
				if (scripts[i] == null)
				{
					return;
				}
			}
			
			clearScript();
			
			_scripts = scripts;
			
			var scriptNum:int = _scriptNum;
			_scriptNum = -1;
			setScriptNum(scriptNum);
			
			if (autoStart)
			{
				startWriting();
			}
		}
		
		public function addScript (script:String , autoStart:Boolean = true):void
		{
			if (!_initComplete || script == null)
			{
				return;
			}
			
			_scripts[_scripts.length] = script;
			
			if (autoStart)
			{
				startWriting();
			}
		}
		
		public function splitScripts ():void
		{
			if (!_initComplete)
			{
				return;
			}
			
			var charsTotal:int;
			var script:String;
			var heightRequired:Number;
			var lineMetrics:TextLineMetrics;
			var j:int;
			var cutLength:int;
			for (var i:int ; i < _scripts.length ; i++)
			{
				script = _scripts[i];
				_textField.text = script;
				heightRequired = 4; //2px gutters * 2
				j = 0;
				while (j < _textField.numLines)
				{
					lineMetrics = _textField.getLineMetrics(j);
					heightRequired += lineMetrics.height;
					
					if (heightRequired > _textField.height)
					{
						break;
					}
					
					charsTotal += _textField.getLineLength(j++);
				}
				
				if (heightRequired <= _textField.height)
				{
					continue;
				}
				
				_textField.text = "";
				
				for (j = charsTotal - 1 ; j >= 0 ; j--)
				{
					if (charIsWhitespace(script.charAt(j))) //Escape is not counted as a character.
					{
						cutLength = 1;
						break;
					}
				}
				
				if (j < 0)
				{
					j = charsTotal;
					cutLength = 0;
				}
				
				_scripts[i] = script.substr(0 , j);
				script = script.substr(j + cutLength);
				if (script.length > 0)
				{
					_scripts.splice(i + 1 , 0 , script);
				}
			}
		}
		
		protected function charIsWhitespace (char:String):Boolean
		{
			return char.search(/[\s\r\n]/) >= 0;
		}
		
		public function incrementScript (autoStart:Boolean = true):void
		{
			setScriptNum(_scriptNum + 1 , autoStart);
		}
		
		public function startWriting ():void
		{
			if (!_initComplete || _writing || _scripts.length == 0 || _textField.text == _scripts[_scriptNum])
			{
				return;
			}
			
			_writing = true;
			
			if (_autoUpdate)
			{
				_textField.addEventListener(Event.ENTER_FRAME , writeFrameHandler);
			}
		}
		
		public function stopWriting ():void
		{
			if (_initComplete && _writing)
			{
				_writing = false;
				_textField.removeEventListener(Event.ENTER_FRAME , writeFrameHandler);
			}
			_timePrev = NaN;
		}
		
		public function showScript ():void
		{
			if (!_initComplete || _scripts.length == 0)
			{
				return;
			}
			
			_textField.text = _scripts[_scriptNum];
			_writePos = _scripts[_scriptNum].length;
			
			stopWriting();
			scriptComplete();
		}
		
		public function clearScript ():void
		{
			if (!_initComplete)
			{
				return;
			}
			
			_writePos = 0;
			_textField.text = "";
			stopWriting();
			_writeComplete = false;
		}
		
		public function removeAllScripts ():void
		{
			if (!_initComplete)
			{
				return;
			}
			
			_scripts = new Vector.<String>();
			_scriptNum = 0;
			clearScript();
		}
		
		public function update (delta:Number):void
		{
			if (!_initComplete || !_writing || _writeComplete || _scripts.length == 0)
			{
				return;
			}
			
			var scriptText:String = _scripts[_scriptNum];
			
			_textField.text = scriptText;
			//Finds the line of the current character after writing is complete.
			var goalLine:int = _textField.getLineIndexOfChar(int(_writePos));
			
			var text:String = scriptText.substr(0 , _writePos + 1);
			_textField.text = text;
			//Finds the line of the current character with only the currently written text being displayed.
			var line:int = _textField.getLineIndexOfChar(int(_writePos));
			
			//If the current line has a lower index than the final line, the character must be part of a word
			//that is being pushed to a new line in the final position.
			if (line < goalLine)
			{
				var wordIndex:int = _writePos;
				while (wordIndex >= 0 && !charIsWhitespace(text.charAt(wordIndex)))
				{
					wordIndex--;
				}
				
				if (wordIndex > 0)
				{
					text = text.substr(0 , ++wordIndex) + "\n" + text.substr(wordIndex);
				}
				
				_textField.text = text;
			}
			
			if (text == scriptText)
			{
				_writePos = int(_writePos);
				stopWriting();
				scriptComplete();
				return;
			}
			
			_writePos += _writeRate * delta;
		}
		
		public function setScriptNum (scriptNum:int , autoStart:Boolean = true):void
		{
			if (!_initComplete || _scriptNum == scriptNum)
			{
				return;
			}
			
			if (scriptNum >= _scripts.length && _scripts.length > 0)
			{
				_scriptNum = _scripts.length == 0 ? 0 : _scripts.length - 1;
				showScript();
				return;
			}
			
			clearScript();
			
			_scriptNum = scriptNum <= 0 ? 0 : scriptNum;
			
			if (autoStart)
			{
				startWriting();
			}
		}
		
		protected function writeFrameHandler (event:Event):void
		{
			var time:Number = new Date().time;
			var delta:Number = 0;
			if (_timePrev > 0)
			{
				delta = time - _timePrev;
			}
			update(delta);
			_timePrev = time;
		}
		
		protected function scriptComplete ():void
		{
			_writeComplete = true;
			dispatchEvent(new Event(SCRIPT_COMPLETE));
			
			if (_scriptNum >= _scripts.length - 1)
			{
				dispatchEvent(new Event(ALL_SCRIPTS_COMPLETE));
			}
		}
		
		public function getTextField():TextField
		{
			return _textField;
		}
		
		public function setTextField(value:TextField):void
		{
			if (!_initComplete)
			{
				_textField = value;
			}
		}
		
		public function getScriptNum():int
		{
			return _scriptNum;
		}
		
		public function getScriptsTotal ():int
		{
			return _scripts.length;
		}
		
		public function getScriptText ():String
		{
			return !_scripts || _scriptNum >= _scripts.length ? null : _scripts[_scriptNum];
		}
		
		public function getWriteRate ():Number
		{
			return _writeRate;
		}
		
		public function setWriteRate (value:Number):void
		{
			_writeRate = value;
		}
		
		public function getWritePos ():Number
		{
			return _writePos;
		}
		
		public function isWriting ():Boolean
		{
			return _writing;
		}
		
		public function isWriteComplete():Boolean
		{
			return _writeComplete;
		}
		
		public function isAutoUpdate():Boolean
		{
			return _autoUpdate;
		}
		
		public function setAutoUpdate(value:Boolean):void
		{
			if (!_initComplete)
			{
				_autoUpdate = value;
			}
		}


	}
	
}